using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;

namespace Charisma.Linq
{
  /// <summary>
  /// An implementation of <see cref="IQueryable{T}"/>.
  /// </summary>
  /// <typeparam name="T">The type of the content item of the data source.</typeparam>
  public sealed class Queryable<T> : IOrderedQueryable<T>
  {
    private readonly IQueryProvider provider;
    private readonly Expression expression;

    /// <inheritdoc/>
    public Expression Expression
    {
      get { return expression; }
    }

    /// <inheritdoc/>
    public Type ElementType
    {
      get { return typeof(T); }
    }

    /// <inheritdoc/>
    IQueryProvider IQueryable.Provider
    {
      get { return provider; }
    }

    #region IEnumerable<...> members

    /// <inheritdoc/>
    public IEnumerator<T> GetEnumerator()
    {
      var result = provider.Execute<IEnumerable<T>>(expression);
      return result.GetEnumerator();
    }

    /// <inheritdoc/>
    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }

    #endregion

    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
      Contract.Invariant(provider != null);
      Contract.Invariant(expression != null);
    }


    // Constructors

    public Queryable(IQueryProvider provider)
    {
      Contract.Requires(provider != null);

      this.provider = provider;
      expression = Expression.Constant(this, typeof(IQueryable<T>));
    }

    /// <exception cref="ArgumentOutOfRangeException"><paramref name="expression"/>  is out of range.</exception>
    public Queryable(IQueryProvider provider, Expression expression)
    {
      Contract.Requires(provider != null);
      Contract.Requires(expression != null);
      
      if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
        throw new ArgumentOutOfRangeException("expression");
      this.provider = provider;
      this.expression = expression;
    }
  }
}